home *** CD-ROM | disk | FTP | other *** search
Text File | 1990-06-25 | 19.9 KB | 415 lines | [TEXT/CCL ] |
- ; (c) Copyright 1990 by University of Massachusetts. All rights reserved.
- ; This software was conceived, designed, and written by Dan Suthers
- ; while supported by the National Science Foundation under grant number
- ; MDR 8751362, and by a fellowship from Apple Computer, Inc., Cupertino,
- ; CA. Partial support was also received from the Office of Naval Research
- ; under a University Research Initiative Grant, contract N00014-86-K-0764.
- ; Mr. Suthers created this software under his own initiative while in an
- ; academic relationship with the University of Massachusetts. The above
- ; copyright notice was a condition placed by University lawyers on approval
- ; of distribution of this software by Apple Computer, and is not meant to
- ; imply that this software was created in an employment or "work for hire"
- ; relationship between the University and Mr. Suthers.
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ; File: Rule-Build.lisp
- ; Author: Dan Suthers
- ; Created: 19-Oct-88 21:57:32
- ; Modified: 22-Jun-90 02:21:38 (Dan Suthers)
- ; Language: Common Lisp
- ; Package: RULE
- ;
- ; Description: Rule-based reasoner built on the pattern matching facilities
- ; of DNET. Supports forward and backward reasoning.
- ;
- ; This file contains only the definition of the SM type RULE,
- ; and functions to add rules to DNETs of rules, delete them,
- ; and related tasks. See Rule-Defs, Rule-Forward, and Rule-Back.
- ; File RULES has documentation.
- ;
- ; (c) Copyright 1988, by Daniel D. Suthers
- ; Department of Computer and Information Science
- ; University of Massachusetts
- ; Amherst, Massachusetts 01003
- ;
- ; This software was conceived, designed, and written by Dan Suthers
- ; while supported by the National Science Foundation under grant number
- ; MDR 8751362, and by a fellowship from Apple Computer, Inc., Cupertino,
- ; CA. Partial support was also received from the Office of Naval Research
- ; under a University Research Initiative Grant, contract N00014-86-K-0764.
- ; I wish to acknowledge the generous support of Beverly Woolf, who obtained
- ; the above grants and encouraged me to pursue my own research interests in
- ; her lab. This work would not have been possible without the resources and
- ; stimulating environment of the Computer and Information Science department.
- ;
- ; Permission to use, modify, and distribute this software is granted subject
- ; to the following restrictions and understandings:
- ; 1. The file header, including this notice, shall be retained, and may be
- ; extended to include documentation of modifications to the software.
- ; 2. This material is for nonprofit educational and research purposes only.
- ; Users are requested, but not required, to inform Mr. Suthers of any
- ; noteworthy uses of this software.
- ; 3. Mr. Suthers and the University of Massachusetts make no warrantee or
- ; representation that the operation of this software will be error free,
- ; and are under no obligation to provide any services.
- ; 4. Any user of such software agrees to indemnify and hold harmless Mr.
- ; Suthers and the University of Massachusetts from all claims arising
- ; out of the use or misuse of this software, or arising out of any
- ; accident, injury, or damage whatsoever, and from all costs, counsel
- ; fees, and liabilities incurred in or about any such claim, action, or
- ; proceeding brought thereon.
- ; 5. All materials and reports developed as a consequence of the use of
- ; this software shall duly acknowledge such use, in accordance with
- ; the usual standards of acknowledging credit in academic research.
- ;
- ; Status: Working.
- ;
- ; Changes:
- ; 30-Dec-88 :DELETE added to forward rules.
- ; 25-Mar-89 :SEQ added; eliminated bogus prohibition of nested :AND.
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (in-package :RULE)
-
- (export '(
- add-rule
- add-all-rules
- delete-rule
-
- rule
- rule-antecedent
- rule-consequent
- rule-directions
-
- ))
-
-
- (require :MISC) ; UTILS for UNIQUE-SYMBOL
- (require :Rule-Defs)
-
- (use-package :DNET)
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;; DATA STRUCTURES
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (sm:dst (RULE (:redefine T)
- (:reusable nil)
- (:sort-instances t)
- (:before-edit (lambda (r)
- (dolist (dnet (rule-interned-in (sm:gets 'rule r)))
- (delete-rule r dnet))))
- (:after-edit (lambda (r)
- (dolist (dnet (rule-interned-in (sm:gets 'rule r)))
- (add-rule r dnet))))
- (:comments "
- Simple if-then pattern matching rules.
- VARIABLES are the same as DNET variables, namely, symbols in the ? package.
- SPECIAL FORMS include:
- (:AND <f1> ... <fn>) - If in the consequent, this is only for convienence, and the
- rule is parsed into multiple rules. If in the antecedent, forward and backward
- chaining tries to process each of <f1> ... <fn> in the order given. Any :AND not
- at top level in the consequent results in factoring of the consequent. This is
- logical conjunction. SUPPORT does not short-circuit evaluation when :record-failure
- is T. This allows applications to identify `near misses', etc.
- (:SEQ <C1> .. <Cn>) - Like :AND, but is always short-circuited on failure, even
- when :record-failure is T. (SEQuential conjunction.)
- (:OR <f1> ... <fn>) - for convienence in the antecedent only. The rule will be
- stored as multiple rules resulting from factoring out :OR. Ignored in the
- consequent.
- (:LISP <expr1> ... <exprN>) - Variables in the current bindings list are
- lambda-bound, and the expressions are evaluated, as in PROGV, the last value
- being returned. When the :LISP form occurs in the antecedent, the result of
- evaluation is used to determine success (whether forward or back chaining). It
- is useful in the consequent when side effects are desired in forward chaining,
- and to insert the results of lisp evaluation in the consequent expression. To
- enable both uses, the result of a :LISP at top level is ignored (not treated as
- a derived expression to be added to the data base dnet); while the result of a
- :lisp embedded in an expression is included in that expression when it is indexed
- as a new datum.
- (:BIND <var> <expr>) - Variables in <expr> are bound, it is evaluated, and the
- result is bound to <var>, which must be a variable. Useful in antecedent to
- define variables used in consequent, eg. to prevent use of :LISP IN consequent
- which needs to be matched to for backchaining.
- (:DELETE <expr>) - Variables in <expr> are bound, and the expression is deleted
- from the active data base. (Any truth maintenance activities will be up to the
- DELEXPR-HOOK of the DNET.) Applies only to forward rules."))
-
- (ANTECEDENT nil
- :type list
- :comments "
- A list expression, which may include variables, :AND, :SEQ, :OR (all possibly
- embedded), :LISP, and/or :BIND. Forms embedded in :AND and :OR must themselves be
- lists or contained in lists after parsing.")
-
- (CONSEQUENT nil
- :type list
- :comments "
- A list expression, which may include variables, :AND and :SEQ (possibly nested),
- :LISP, and/or :BIND. Forms embedded in :and must be lists, unless the :and is also
- embedded in a list after parsing.")
-
- (DIRECTIONS :both
- :type (member :forward :forward-unique :backward :both :both-unique)
- :comments "
- Which direction(s) to use the rule in. :FORWARD-UNIQUE and :BOTH-UNIQUE specify
- that the rule will not be allowed to fire forward twice on the same bindings.
- This takes extra space to save a list of previous bindings, and time to check new
- bindings against it. Note that rules whose consequents are simple expressions
- to be added to a data base will not be allowed to add the same expression twice,
- even if :FORWARD or :BOTH is specified. The only time you need :FORWARD-UNIQUE
- is when the consequent has a :LISP form which you only want to be evaluated once
- on a given binding set. There is no :backward-unique since backchaining does
- not work on :lisp consequents.")
-
- (INTERNED-IN nil
- :type list
- :comments "
- List of DNETs the rule is interned (stored) in. Updated automatically, but not
- declared :computed in case you want to store and change it.")
-
- (INFO nil
- :type list
- :comments "
- Association list of keys to arbitrary info needed by the application.")
-
- (COMMENTS "" :type string))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;; INTERNAL FUNCTIONS AND MACROS
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Little Helpers
-
- (defun TREE-MEMBER (thing tree)
- (declare (keyword thing) (optimize (safety 1) (space 2) (speed 3)))
- (cond ((atom tree) (eq thing tree))
- ((null tree) nil)
- (t (or (tree-member thing (car tree))
- (tree-member thing (cdr tree))))))
-
- ;;;------------------------------------------------------------------------
- ;;; Rule Error Checking:
- ;;; - :antecedent and :consequent are consed to forms to indexpr, to distinuish
- ;;; these patterns while matching. This is why the factored forms must be lists:
- ;;; DNET cannot store a dotted pair.
- ;;; - An :OR in a consequent won't be interpreted, so probably is an error. But
- ;;; I only WARN, since they may be in there for reference rather than for use
- ;;; (e.g. for rules manipulating rules).
-
- (defun BAD-ANTECEDENT-FORMS (rule antecedent-forms)
- (declare (symbol rule) (list antecedent-forms)
- (optimize (safety 1) (space 2) (speed 3)))
- (cond ((not (every #'listp antecedent-forms))
- (cerror "Rule will not be added."
- "[DNET:ADD-RULE] Parsed antecedent of Rule ~S contains non-list: ~%~S"
- rule antecedent-forms)
- t)
- ((not (every #'dnet:not-a-dotted-list antecedent-forms))
- (cerror "Rule will not be added."
- "[DNET:ADD-RULE] Rule ~S has illegal dotted list in antecedent:~%~S"
- rule antecedent-forms)
- t)
- (t nil)))
- (proclaim '(inline bad-antecedent-forms))
-
- (defun BAD-CONSEQUENT-FORMS (rule consequent-forms)
- (declare (symbol rule) (list consequent-forms)
- (optimize (safety 1) (space 2) (speed 3)))
- (cond ((not (every #'listp consequent-forms))
- (cerror "Rule will not be added."
- "[DNET:ADD-RULE] Parsed consequent of Rule ~S contains non-list: ~%~S"
- rule consequent-forms)
- t)
- ((not (every #'dnet:not-a-dotted-list consequent-forms))
- (cerror "Rule will not be added."
- "[DNET:ADD-RULE] Rule ~S has illegal dotted list in consequent:~%~S"
- rule consequent-forms)
- t)
- (t nil)))
- (proclaim '(inline bad-consequent-forms))
-
- (defun WARN-OF-OR-IN-CONSEQUENT (rule consequent-forms)
- (when (some #'(lambda (form) (declare (list form) (inline tree-member))
- (tree-member :or form))
- consequent-forms)
- (warn "[DNET:ADD-RULE] :OR in consequent of Rule ~S won't be interpreted."
- rule)
- t))
- (proclaim '(inline warn-of-or-in-consequent))
-
- ;;;------------------------------------------------------------------------
- ;;; Review of Rule Indexing Policy and Representation:
- ;;; - :or-factored antecedents --forward--> conjunct of :and-factored consequents
- ;;; - :and-factored consequents --backward--> disjunct of unfactored antecedents
- ;;; - If a pattern is already indexed, we union the implicit conjunct/disjunct lists
- ;;; to be mapped to. This allows multiple rules to address the same data.
- ;;; - Each member of the implicit conjunct/disjunct indexed to is a rule record,
- ;;; recording among other things the rule which generated it (the warrant) and
- ;;; the consequent/antecedent mapped to.
- ;;; - Care should be taken to place conjuncts/disjuncts in the same order in this
- ;;; list as they were in the original unfactored rule.
-
- (defun ADD-RULE-INTERNAL (rule dnet)
- (declare (symbol rule dnet)
- (function factor unique-variable-substitutions index-rule)
- (optimize (safety 1) (space 2) (speed 3)))
-
- (defun FACTOR (operator pattern &aux (results (list :head)))
- ;; Returns a list of expressions which have been factored by <operator>, eg:
- ;; (factor :and '(:and (:and a b) (:and (foo c) d))) => (a b (foo c) d))
- (declare (keyword operator) (list results))
- (cond ((null pattern) (nconc results (list nil)))
- ((atom pattern) (nconc results (list pattern)))
- ((eq operator (first pattern))
- (dolist (pat (rest pattern))
- (nconc results (factor operator pat))))
- (t
- (dolist (car-factor (factor operator (car pattern)))
- (dolist (cdr-factor (factor operator (cdr pattern)))
- (nconc results (list (cons car-factor cdr-factor)))))))
- (cdr results))
-
- (defun UNIQUE-VARIABLE-SUBSTITUTIONS (form)
- ;; Returns a binding list which represents a substitution which should be
- ;; done to result in an expression using unique variables.
- (mapcar #'(lambda (var &aux new-var)
- (declare (symbol var new-var))
- (setq new-var
- (utils:unique-symbol (format nil "~A." var) *?-package*))
- (export new-var *?-package*)
- (cons var new-var))
- (variables-in-pattern form)))
-
- (defun INDEX-RULE (dnet key index-forms info-forms)
- ;; Indexes each of <index-forms> into <dnet>, with <info-forms> recorded
- ;; as info. <Key> is consed onto each <index-form> for indexing. If a
- ;; collision occurs on indexing, unions the <info-form>s.
- (declare (symbol dnet) (keyword key) (list index-forms info-forms))
- (dolist (index-form index-forms)
- (declare (list index-form))
- (multiple-value-bind
- (newly-added terminal)
- ;; Don't use template, so stored expression is not altered with template.
- (dnet::indexpr-internal (cons key index-form) dnet info-forms)
- (unless newly-added
- (setf (dnet-terminal-info terminal)
- ;; Destructive safe since fresh list made by mapcar and list below.
- (nunion info-forms
- (dnet-terminal-info terminal)
- :test #'(lambda (r1 r2)
- (declare (list r1) (list r2))
- (and (eq (rule-record-rule-name r1)
- (rule-record-rule-name r2))
- (equal (rule-record-pattern r1)
- (rule-record-pattern r2))))))))))
-
- ;; The body of ADD-RULE-INTERNAL:
- (let* ((rule-struct (sm:gets 'rule rule))
- (substitutions
- (unique-variable-substitutions
- (append (rule-antecedent rule-struct) (rule-consequent rule-struct))))
- (unique-antecedent
- (substitute-bindings substitutions (rule-antecedent rule-struct)))
- (unique-consequent
- (substitute-bindings substitutions (rule-consequent rule-struct)))
- (antecedent-forms (factor ':or unique-antecedent))
- (consequent-forms (mapcan #'(lambda (form) (factor ':seq form))
- (factor ':and unique-consequent))))
- (declare (list substitutions unique-antecedent unique-consequent
- antecedent-forms consequent-forms))
- (cond
- ((bad-antecedent-forms rule antecedent-forms) nil)
- ((bad-consequent-forms rule consequent-forms) nil)
- (T
- (warn-of-or-in-consequent rule consequent-forms)
- (case (rule-directions rule-struct)
- ((:both)
- (index-rule dnet :antecedent antecedent-forms
- (mapcar #'(lambda (c) (make-rule-record rule c nil))
- consequent-forms))
- (index-rule dnet :consequent consequent-forms
- (list (make-rule-record rule unique-antecedent nil))))
- ((:forward)
- (index-rule dnet :antecedent antecedent-forms
- (mapcar #'(lambda (c) (make-rule-record rule c nil))
- consequent-forms)))
- ((:forward-unique)
- (index-rule dnet :antecedent antecedent-forms
- (mapcar #'(lambda (c) (make-rule-record rule c t))
- consequent-forms)))
- ((:backward)
- (index-rule dnet :consequent consequent-forms
- (list (make-rule-record rule unique-antecedent nil)))))
- (pushnew dnet (rule-interned-in (sm:gets 'rule rule)))
- rule))))
-
- (defun DELETE-RULE-INTERNAL (rule dnet)
- ;; This has to take the brute-force approach, since we don't know what
- ;; variable replacement was done to uniquify it.
- (declare (symbol rule dnet) (optimize (safety 1) (space 2) (speed 3)))
- (map-dnet-terminals
- #'(lambda (terminal)
- ;; When we get a terminal which references this rule ...
- (when (member rule (dnet-terminal-info terminal)
- :key #'rule-record-rule-name)
- ;; ... remove all rule-records belonging to this rule ...
- (setf (dnet-terminal-info terminal)
- (delete rule (dnet-terminal-info terminal)
- :key #'rule-record-rule-name))
- ;; .. if there are no more rule records, unindex the entry.
- (unless (dnet-terminal-info terminal)
- (dnet::delexpr-internal (dnet-terminal-expr terminal) dnet))))
- dnet))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;; USER INTERFACE FUNCTIONS
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (defun ADD-RULE (rule dnet)
- "add-rule <rule> <dnet> [Function]
- The <rule> should be the symbolic name of an SM rule instance, and <dnet>
- the name of a dnet. The rule will be added to the <dnet>, possibly after
- some rule transformations on :and and :or. <Rule> is returned."
- (declare (inline add-rule-internal))
- (check-type rule symbol)
- (check-type dnet symbol)
- (assert (sm:gets 'rule rule) (rule) "[DNET:ADD-RULE] ~S is not a known RULE." rule)
- (assert (sm:gets 'dnet dnet) (dnet) "[DNET:ADD-RULE] ~S is not a known DNET." dnet)
- ;; Further error checking done in here after parsing the antecedent and consequent.
- (add-rule-internal rule dnet))
-
- (defun ADD-ALL-RULES ()
- "add-all-rules [Function]
- Adds all known rules to the DNETs specified in their INTERNED-IN slots.
- If a DNET does not exist, continuable error allows user to create it."
- (dolist (r (sm:instances 'rule))
- (dolist (dnet (rule-interned-in (sm:gets 'rule r)))
- (if (sm:gets 'dnet dnet)
- (add-rule r dnet)
- (progn
- (cerror "Will make a plain-vanilla DNET by that name."
- "[DNET:ADD-ALL-RULES] ~S is not a known DNET." dnet)
- (make-dnet dnet)
- (add-rule r dnet))))))
-
- (defun DELETE-RULE (rule dnet)
- "delete-rule <rule> <dnet> [Function]
- Undoes effects of ADD-RULE."
- (declare (inline delete-rule-internal))
- (check-type rule symbol)
- (check-type dnet symbol)
- (assert (sm:gets 'rule rule) (rule)
- "[DNET:DELETE-RULE] ~S is not a known RULE." rule)
- (assert (sm:gets 'dnet dnet) (dnet)
- "[DNET:DELETE-RULE] ~S is not a known DNET." dnet)
- (delete-rule-internal rule dnet))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- (provide :rule-build)
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; the end.